"""
    Minimalistic HTTP multipart POST support. 
    
    Transfer filelike data to the servers. Python 2.2 compatible.     
    Suitable for embedded environments: Fixed size low memory footprint,
    no extra data processing costs.
    
    Consider this code as beta quality. 
    
    Adapted from the orignal:
        http://www.velocityreviews.com/forums/t335298-multipartformdata-in-an-http-client.html
    
 
"""
import os
import httplib
import stat

__author__ = "Mikko Ohtamaa <mikko@redinnovation.com>"
__docformat__ = "epytext"
__copyright__ = "Red Innovation Ltd."
__license__ = "BSD"

#: How much data we yield to socket.sendall() once
SEND_BUFFER_SIZE = 32000

def debug_output(msg):
    """ Monkey patch this function to print debug output 
    
    Example:
    
        import simplemultipart
    
        simplemultipart.debug_output = logger.debug
    
    @param msg Debug message string to output
    """
    pass

class SimuWriter:
    """ Simulate writing process to get the total content length of the data. """
    
    def __init__(self):
        self.length = 0
    
    def write(self, bytes):
        self.length += len(bytes)
        
    def add(self, size):
        self.length += size
        
    def add_file(self, file):
        self.length += os.stat(file)[stat.ST_SIZE] 
        
class SocketWriter:
    """ Output data directly to the HTTP connection (socket). """
        
    def __init__(self, conn, total_length):
        self.conn = conn
        self.done = 0
        self.total_length = total_length
    
    def write(self, data):            
        self.conn.send(data)
        self.done += len(data)
        
def post_multipart(host, selector, fields, files, send_callback):
    """ HTTP POST with file attachments.
    
    Post multiple fields and files to a HTTP host as multipart/form-data.
    
    First run a simulation which will calculate the total content length of the 
    resulting post. Then do HTTP POST and write data to the wire with a minimum 
    buffer to avoid memory consumption.
    
    Fields are tuples of (string field name, string value).
    
    Files are tuples of (string field name, string filename, string filename or direct value).
    If file value is an existing full path to the string (os.path.exists checked) then it's
    opened as a file and then streamed to the server. If the os.path.exists check
    fails then the value is treated as the contents of the file.
    
    Example::
    
        import simplemultipart
        import urlparse # get urlparse.py from Python 2.2 distribution
        
        files = [ 
            ("this_is_a_http_post_field_id", "filename_appering_in_post.txt", "c:sample.txt") 
        ]
    
        fields = [
            ( "username" , username ),
            ( "password" , password ),        
        ]
        
        def send_callback(action, bytes_done, bytes_total, current_filename):
            # Send callback allows the progress report and thread yielding
            #
            # @param  action string what's happening ('transferring')
            # @param bytes_done How many bytes have we sent
            # @param bytes_total How many bytes is the post size
            # @param current_filename Currently sending this file
            #
            print "Sending %s %d/%d" % (bytes_done, bytes_total, current_filename)
        
        scheme, host, path, parameters, query, fragment = urlparse.urlparse(server_url)
    
        if debug_output != None:
            debug_output("Doing POST to %s,%s" % (host, query))     
            
        response = simplemultipart.post_multipart(host, path, fields, files, send_callback)
        response = response.read()
        
    @param host Host name as string. IP address or domain name.
    @param selector URI on the host as string. E.g.  /myscript.py
    @param fields fields is a sequence of (name, value) elements for regular form fields.
    @param files is a sequence of (name, filename, value) elements for data to be uploaded as files.
    @param send_callback function for progress reporting, see the example.
    
    @return httplib.HTTPResponse object.
    
    @raise IOError when there are socket problems
    """
    
    #
    # TODO: Make a proper File wrapper class instead of using ugly 
    # value tuples! 
    #
    
    debug_output("Creating POST data")
    
    # First do dry run to get the POST length
    writer = SimuWriter()
    content_type = encode_multipart_formdata(writer, fields, files, send_callback)
    content_length = writer.length
    
    # Then do the actual post
    protocol = host.split(':')[0]
    h = httplib.HTTPConnection(host)
        
    debug_output("Sending POST to " + host + " " + selector + " total len:" + str(content_length))
    h.putrequest('POST', selector)
    h.putheader('Content-type', content_type)
    h.putheader('Content-length', content_length)
    h.endheaders()
    writer = SocketWriter(h, content_length)    
    debug_output("Starting HTTP connection")
    encode_multipart_formdata(writer, fields, files, send_callback)
    
    if writer.done != writer.total_length:
        # Check that we didn't screw up anything
        raise RuntimeError("Did not sent all bytes %d / %d" % (writer.done, writer.total_length))

    response = h.getresponse()    
    
    return response

def encode_multipart_formdata(buf, fields, files, send_callback):
    """ Create multipart POST request.
    
    @param buf File like object
    @param fields sequence of tuples
    @param files sequence of tuples
    @param send_callback function
    
    fields is a sequence of (name, value) elements for regular form fields.
    files is a sequence of (name, filename, value) elements for data to be uploaded as files.
    Return (content_type, body) ready for httplib.HTTPConnection instance
    """
    BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_---$---'
    
    # BOUNDARY = mimetools.choose_boundary()
    CRLF = '\r\n'
    
    debug_output("Outputting fields")
    for (key, value) in fields:
        buf.write('--' + BOUNDARY + CRLF)
        buf.write('Content-Disposition: form-data; name="%s"' % key + CRLF)
        buf.write('' + CRLF)
        buf.write(value + CRLF)
        
    debug_output("Outputting files")
    count = 0
    for (key, filename, value) in files:        
        buf.write('--' + BOUNDARY + CRLF)
        buf.write('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename) + CRLF)
        buf.write(''+ CRLF)
        
        if os.path.exists(value):
            # Read the file            
            dirname, fname = os.path.split(value)
                
            if isinstance(buf, SimuWriter):
                # Quick add file size
                buf.add_file(value)                
            else:
                debug_output("Sending file %d. %s" % (count, value))
                # Send the actual file over the wire            
                f = open(value)         
                data = f.read(SEND_BUFFER_SIZE)   
                while(data != ""):                    
                    buf.write(data)
                    data = f.read(SEND_BUFFER_SIZE)
                    if send_callback != None:                    
                        send_callback("transfer", buf.done, buf.total_length, fname)                    
                f.close()
            buf.write(value + CRLF)
                        
        else:
            # Direct string value
            buf.write(value)
        
        count += 1
        buf.write('' + CRLF)
        
    buf.write('--' + BOUNDARY + '--' + CRLF)
    buf.write('' + CRLF)
            
    content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
    return content_type
